package controller;

import java.awt.event.*;
import physics.Angle;
import physics.Geometry.DoublePair;
import physics.Vect;
import model.*;



class AnimationEventListener extends MouseAdapter 
	implements MouseMotionListener, KeyListener, ActionListener {
	
	// overview: AnimationEventListener is now a MVC Controller class that
	// responds to all sorts of external events - timer, mouse and keyboard.
	// It owns, and sends semantic actions to the ball.

	Ball ball;
	Board board;
	Vect r = null;

	public AnimationEventListener(Ball ball, Board board) {
		super();
		this.ball = ball;
		this.board = board;
	}

	public void mouseClicked(MouseEvent e) {}
	public void mouseDragged(MouseEvent e) {}
	public void mouseMoved(MouseEvent e) {}

	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == 71)
			ball.setGravity(!ball.gravityEnabled());
		if (e.getKeyCode() == 72)
			ball.reset();
	}

	public void keyReleased(KeyEvent e) {
		if (e.getKeyCode() == 73) {
			for (IGizmo each : board.getGizmos()) {
				if (each instanceof LeftFlipper)
					each.triggerEvent();
				if (each instanceof RightFlipper)
                    each.triggerEvent();

			}
		}
	}
	public void keyTyped(KeyEvent e) {}

	// this is the callback for the timer
	public void actionPerformed(ActionEvent e) {
		
//		double stuc = timeTillNextCollision();
//		/*
//		 * symmetrical bounce ignoring gravity.
//		 * ball essentially just mirrored after collision 
//		 */
//		if (stuc < 1.0) {
//			while (true) {
//				ball.move(stuc);
//				ball.setV(r);
//				double next = timeTillNextCollision();
//				if (stuc < next) {
//					ball.move(stuc);
//					break;
//				}
//				else
//					stuc = next;
//			}
//		} 
//		else ball.move(1.0);
		
		
//		double stuc = timeTillNextCollision();
//		/*
//		 * asymmetrical bounce with gravity.
//		 * ball moved for fractions of time
//		 */
//		if (stuc < 1) {
//			double timeRemaining = 1.0;
//			while (timeRemaining > 0) {
//				ball.move(stuc);
//				Vect b = ball.getV(); 
//				Angle a = r.angle().minus(b.angle()); 
//				ball.setV(b.rotateBy(a));
//				timeRemaining -= stuc;
//				stuc = timeTillNextCollision();
//				if (stuc > timeRemaining) {
//					ball.move(timeRemaining);
//					break;
//				}
//			}
//		}
//		else ball.move(1.0);
		
	
		/*
		 * asymmetrical bounce with gravity.
		 * ball moved for fractions of time using passed objects
		 */
		Pair<Double, Object> nCO = getNextCollidingObject();
		double stuc = nCO.time;
		if (stuc < 1) {
			double timeRemaining = 1.0;
			while (timeRemaining > 0) {
				ball.move(stuc);
				if (nCO.object instanceof Absorber)
				{
					
					ball.setPos(19.5*board.getL(), 6*board.getL());
					ball.setV(new Vect(0.0, 37));
					
					
				}
				if (nCO.object instanceof IGizmo) {
					((IGizmo)nCO.object).triggerEvent();
					r = ((IGizmo)nCO.object).reflectionVector(ball);
				}
				else
					r = board.reflectionVector(ball);
				Vect b = ball.getV(); 
				Angle a = r.angle().minus(b.angle()); 
				ball.setV(b.rotateBy(a));
				timeRemaining -= stuc;
				nCO = getNextCollidingObject();
				stuc = nCO.time;
				if (stuc > timeRemaining) {
					ball.move(timeRemaining);
					break;
				}
			}
		}
		else 
			{
			System.out.println(ball.getV());
			ball.move(1.0);
			}
	
	}// end of actionPerformed()
	
	
	public double timeTillNextCollision() {
		double stuc=Double.POSITIVE_INFINITY, t=0;
		
		t = board.timeUntilCollision(ball);
		if (t < stuc) {
			stuc = t;
			r = board.reflectionVector(ball);
		}
		
		for (IGizmo each : board.getGizmos()) {
			t = each.timeUntilCollision(ball);
			if (t < stuc) {
				stuc = t;
				r = each.reflectionVector(ball);
			}
		}
		return stuc;
	}
	
	
	public Pair<Double, Object> getNextCollidingObject() {
		double stuc=Double.POSITIVE_INFINITY, t=0;
		Object object = null;
		
		t = board.timeUntilCollision(ball);
		if (t < stuc) {
			stuc = t;
			object = board;
		}
		
		for (IGizmo each : board.getGizmos()) {
			t = each.timeUntilCollision(ball);
			if (t < stuc) {
				stuc = t;
				object = each;
			}
		}
		return new Pair<Double, Object>(stuc, object);
	}
	
	
	/*
	 * A throw-away class that allows us to return a tuple, in this
	 * case, the time until next collision and the corresponding object.
	 * Saves us from recalculating values needlessly.
	 * Object could be board instance if next collision is with a wall. 
	 */
	class Pair<A, B> {
		public final A time;
		public final B object;
		public Pair(A first, B second) {
			time = first;
			object = second;
		}
	}



}
